package com.gridpower.spreadsheet.client.widgets;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.WindowResizeListener;

import com.google.gwt.widgetideas.graphics.client.GWTCanvas;
import com.gridpower.spreadsheet.client.models.DocModel;
import com.gridpower.spreadsheet.client.models.AppModel;
import com.gridpower.spreadsheet.client.models.GridCell;
import com.gridpower.spreadsheet.client.models.AppModel.CellSelection;
import com.gridpower.spreadsheet.client.models.AppModel.DocModelChangeListener;
import com.gridpower.spreadsheet.client.widgets.GridPart.Rect;
import com.gridpower.spreadsheet.client.widgets.GridPart.Size;
/**
 * every sub part deal with the UI event.
 * not, the decorator and mouse status may conflict(not true)
 * @author 438
 * browser's window resize listener.
 */
public class AppView implements WindowResizeListener {

	/**
	 * parent holder and specially the bottom right should be held by a
	 * scroller panel.
	 */
	protected AbsolutePanel viewHolder = new AbsolutePanel();
	protected GridPart topLeftPart = new GridPart();
	protected GridPart topRightPart = new GridPart();
	protected GridPart botLeftPart = new GridPart();
	protected GridPart botRightPart = new GridPart();
	protected AbsolutePanel topLeftHolder = new AbsolutePanel();
	protected AbsolutePanel topRightHolder = new AbsolutePanel();
	protected AbsolutePanel botLeftHolder = new AbsolutePanel();
	protected AbsolutePanel botRightHolder = new AbsolutePanel();
	
	protected ScrollPanel virtualVertScroller = new ScrollPanel() {
		@Override
		public void onBrowserEvent(Event evt) {//test
		//	GWT.log(Integer.toString(DOM.eventGetType(evt)), null);
		//	System.out.println("view"+DOM.eventGetType(evt));
			if (evt != null) {
				switch (DOM.eventGetType(evt)) {
					case Event.ONSCROLL: {
						int vertPos = this.getScrollPosition();
						int horiPos = virtualHoriScroller.getHorizontalScrollPosition();
						//int horiPos = this.getHorizontalScrollPosition();
						//topRightHolder.setWidgetPosition(topRightPart.getHeldPanel(),
						//		-horiPos, 0);
						
						botLeftHolder.setWidgetPosition(botLeftPart.getHeldPanel(),
								0, -vertPos);
						botRightHolder.setWidgetPosition(botRightPart.getHeldPanel(),
								-horiPos, -vertPos);
						//horiRuler.setViewableLeft(horiPos);
						vertRuler.setViewableTop(vertPos);
						calcViewableRowCol(botRightPart.getPosInAppView());
						renderAll();
						break;
					}
				}
			} else {
				int vertPos = this.getScrollPosition();
				int horiPos = virtualHoriScroller.getHorizontalScrollPosition();
				//int horiPos = this.getHorizontalScrollPosition();
				//topRightHolder.setWidgetPosition(topRightPart.getHeldPanel(),
				//		-horiPos, 0);
				
				botLeftHolder.setWidgetPosition(botLeftPart.getHeldPanel(),
						0, -vertPos);
				botRightHolder.setWidgetPosition(botRightPart.getHeldPanel(),
						-horiPos, -vertPos);
				//horiRuler.setViewableLeft(horiPos);
				vertRuler.setViewableTop(vertPos);
				calcViewableRowCol(botRightPart.getPosInAppView());
				renderAll();
			}
		}
	};
	protected ScrollPanel virtualHoriScroller = new ScrollPanel() {
		@Override
		public void onBrowserEvent(Event evt) {//test
		//	GWT.log(Integer.toString(DOM.eventGetType(evt)), null);
		//	System.out.println("view"+DOM.eventGetType(evt));
			switch (DOM.eventGetType(evt)) {
				case Event.ONSCROLL: {
					int vertPos = virtualVertScroller.getScrollPosition();
					int horiPos = this.getHorizontalScrollPosition();
					topRightHolder.setWidgetPosition(topRightPart.getHeldPanel(),
							-horiPos, 0);
					botRightHolder.setWidgetPosition(botRightPart.getHeldPanel(),
							-horiPos, -vertPos);
					//botLeftHolder.setWidgetPosition(botLeftPart.getHeldPanel(),
							//0, -vertPos);
					horiRuler.setViewableLeft(horiPos);
					//vertRuler.setViewableTop(vertPos);
					calcViewableRowCol(botRightPart.getPosInAppView());
					renderAll();
					break;
				}
			 }
			//super.onBrowserEvent(evt);
		}
	};
	/**
	 * the two rulers
	 */
	protected HoriRuler horiRuler = new HoriRuler();
	protected VertRuler vertRuler = new VertRuler();

	
	/**
	 * helper data structure: the helper data structure for UI
	 * components
	 */
	// helper structure indicate each row/col starts, the
	// list size is one larger than row/col number.
	protected ArrayList rowStarts = new ArrayList();
	protected ArrayList colStarts = new ArrayList();
	protected int viewableRowStart = 0;
	protected int viewableRowEnd = 0;
	protected int viewableColStart = 0;
	protected int viewableColEnd = 0;
	
	// the text layer divs, include the divs on the rulers
	protected Map rowCol2Divs = new TreeMap();
	protected Map horiRulerDivs = new HashMap();
	protected Map vertRulerDivs = new HashMap();
	/**
	 * DocModel Change Listener
	 */
	protected DocModelChangeListener docModelListener = new DocModelChangeListener() {
		public void runInternal(int docId) {
			onDocModelChange(docId);
		}
	};

	
	// test
	private Timer timer = new Timer () {
		int row = 0, col= 0;
		int del = 1;
		public void run() {
			System.out.println("timer trig!");
			CellSelection sel = AppModel.getInstance().getSelectModel().getCurrentSelect();
			sel.rowStart = row;
			sel.colStart = col;
			sel.rowEnd = row+1;
			sel.colEnd = col + 1;
			//row += del;
			col += del;
			if (row + 1 == 20 -2
					|| col + 1 == 20 -2)
				del = -del;
			if (/*row == 0 ||*/ col == 0)
				del = -del;
			AppModel.getInstance().getSelectModel().fireModelChange();
		}
	};
	
	public AppView() {
		// set special part
		botRightPart.setGridPartId("botRight");
		
		//viewHolder.ad
		AppModel.getInstance().addDocChangeListener(docModelListener);
		// add each GridPart
		topLeftHolder.add(topLeftPart.getHeldPanel());
		viewHolder.add(topLeftHolder);

		topRightHolder.add(topRightPart.getHeldPanel());
		viewHolder.add(topRightHolder);

		botLeftHolder.add(botLeftPart.getHeldPanel());
		viewHolder.add(botLeftHolder);

		
		botRightHolder.add(botRightPart.getHeldPanel());
		viewHolder.add(botRightHolder);
		
		viewHolder.add(horiRuler.getHeldPanel());
		viewHolder.add(vertRuler.getHeldPanel());
		Window.addWindowResizeListener(this);
		
		virtualHoriScroller.add(new AbsolutePanel());
		virtualVertScroller.add(new AbsolutePanel());
		viewHolder.add(virtualHoriScroller);
		viewHolder.add(virtualVertScroller);

		// test
		//botRightHolder.sinkEvents(/*Event.ONSCROLL);//*/DOM.getEventsSunk(botRightHolder.getElement())|Event.MOUSEEVENTS | Event.ONCLICK);
		disableTextSelection(RootPanel.getBodyElement());//so slow
		
		// test
		//timer.scheduleRepeating(30);

	}
	/**
	 * retrieve a div element wich can be write text on
	 * @param row indicate the row needed to be written
	 * @param col indicate the col needed to be written
	 * @return the div element
	 */
	public Element getCellTextElement(int row, int col) {
		TreeMap rowElems = (TreeMap)rowCol2Divs.get(row);
		if (rowElems == null) {
			rowElems = new TreeMap();
			rowCol2Divs.put(row, rowElems);
		}
		Element elem = (Element)rowElems.get(col);
		if (elem == null) {
			elem = UIUtil.getElementPool().getElement("div");
			rowElems.put(col, elem);
		}
		return elem;
	}
	public Element getHoriRulerTextElement(int col) {
		Element elem = (Element)horiRulerDivs.get(col);
		if (elem == null) {
			elem = UIUtil.getElementPool().getElement("div");
			horiRulerDivs.put(col, elem);
		}
		return elem;
	}
	public Element getVertRulerTextElement(int row) {
		Element elem = (Element)vertRulerDivs.get(row);
		if (elem == null) {
			elem = UIUtil.getElementPool().getElement("div");
			vertRulerDivs.put(row, elem);
		}
		return elem;
	}
	/**
	 * rerender the whole view after helper data calculated
	 */
	public void renderAll() {
		// add all its subparts
		horiRuler.render();
		vertRuler.render();
		topLeftPart.render();
		topRightPart.render();
		botLeftPart.render();
		botRightPart.render();
	}

	///////////////////
	// Outside event sourcing
	///////////////////
	
	///////////////////
	// event handlers(listener)
	///////////////
	/**
	 * recalculate and redraw when doc model changes
	 */
	public void onDocModelChange(int docId) {
		// reCalculate the help data
		// set grid cells' row/col starts and ends
		reCalculateRowColStarts();
		// set cell range
		setGridCellRange();
		// reCalcLayout
		reCalcLayout();
		// redraw
		renderAll();
	}
	/**
	 * recalculate and redraw when the browser window redraw
	 */
	public void onWindowResized(int width, int height) {
		this.viewHolder.setWidth(width-18+"px");
		reCalcLayout();
		renderAll();
	}
	///////////////////
	// local helpers
	///////////////
	/**
	 * calculate the cell range for each grid panel
	 */
	public void setGridCellRange() {
		DocModel docModel = AppModel.getInstance().getCurrentDoc();
		int fixedCol = docModel.getFixedColCount();
		int fixedRow = docModel.getFixedRowCount();
		int colCnt = docModel.getColCount();
		int rowCnt = docModel.getRowCount();
		
		topLeftPart.setCellRangeEndRow(fixedRow-1);// can be -1
		topLeftPart.setCellRangeEndCol(fixedCol-1);
		topRightPart.setCellRangeEndRow(fixedRow-1);
		topRightPart.setCellRangeStartCol(fixedCol);
		topRightPart.setCellRangeEndCol(colCnt-1);
		
		botLeftPart.setCellRangeStartRow(fixedRow);
		botLeftPart.setCellRangeEndRow(rowCnt-1);
		botLeftPart.setCellRangeEndCol(fixedCol-1);
		
		botRightPart.setCellRangeStartRow(fixedRow);
		botRightPart.setCellRangeStartCol(fixedCol);
		botRightPart.setCellRangeEndRow(rowCnt-1);
		botRightPart.setCellRangeEndCol(colCnt-1);
		
	}
	/**
	 * reCalcLayout: calculate each grid panel's position and size
	 *               calculate each grid panel's content size(meaning
	 *                what the size of the whole content belongs to this grid panel)
	 * todo:refactor
	 */
	public void reCalcLayout() {
		// test: set the 
		viewHolder.setHeight("600px");
		DocModel docModel = AppModel.getInstance().getCurrentDoc();
		int scrollWid = 18;
		int colCnt = docModel.getColCount();
		int rowCnt = docModel.getRowCount();
		int fixedCol = docModel.getFixedColCount();
		int fixedRow = docModel.getFixedRowCount();
		int width = viewHolder.getOffsetWidth();// - scrollWid;
		int height = viewHolder.getOffsetHeight();// - scrollWid;
		int splitx = (Integer)colStarts.get(fixedCol);
		int splity = (Integer)rowStarts.get(fixedRow);

		topLeftPart.setContentSize(new Size(splitx, splity));//overlap
		topRightPart.setContentSize(new Size((Integer)colStarts.get(colCnt)-splitx+1, splity));
		botLeftPart.setContentSize(new Size(splitx, (Integer)rowStarts.get(rowCnt)-splity+1));
		botRightPart.setContentSize(new Size((Integer)colStarts.get(colCnt)-splitx+1,
				(Integer)rowStarts.get(rowCnt)-splity+1));
		
		
		int biasY = horiRuler.getHeight();
		int biasX = vertRuler.getWidth();
		Rect pos = new Rect(biasX, biasY, splitx, splity);//overlap
		topLeftPart.setPosInAppView(pos, false);
		pos = new Rect(splitx+biasX, biasY, width-splitx-biasX, splity);
		topRightPart.setPosInAppView(pos, false);

		biasY +=  vertRuler.getSeperatorHeight();
		pos = new Rect(biasX, splity + biasY, splitx, height-splity-biasY);
		botLeftPart.setPosInAppView(pos, false);
		pos = new Rect(splitx+biasX, splity+biasY, width-splitx-biasX, height-splity-biasY);
		botRightPart.setPosInAppView(pos, true);//consider scroll bar!
		
		//pos = new Rect(0, 0, width, biasY);
		horiRuler.setWidth(width);
		vertRuler.setHeight(height);
		// calculate viewable row/col starts and ends 
		calcViewableRowCol(pos);
		
		setPosition(topLeftPart, topLeftHolder);
		setPosition(topRightPart, topRightHolder);
		setPosition(botLeftPart, botLeftHolder);
		setPosition(botRightPart, botRightHolder);
		// set the virtual scroller
		
		virtualHoriScroller.getWidget().setSize((Integer)colStarts.get(colCnt)-splitx+1 + "px", scrollWid+"px");
		virtualVertScroller.getWidget().setSize(scrollWid+"px", (Integer)rowStarts.get(rowCnt)-splity+1+"px");
		virtualHoriScroller.setSize(width-splitx-biasX+"px", scrollWid+"px");
		virtualVertScroller.setSize(scrollWid+"px", height-splity-biasY+"px");
		viewHolder.setWidgetPosition(virtualHoriScroller, splitx+biasX, height-scrollWid);
		viewHolder.setWidgetPosition(virtualVertScroller, width-scrollWid, splity+biasY);
/*
		virtualHoriScroller.setSize(width-splitx-biasX+"px", height-splity-biasY+"px");
		virtualVertScroller.setSize(width-splitx-biasX+"px", height-splity-biasY+"px");
		viewHolder.setWidgetPosition(virtualHoriScroller, splitx+biasX, splity+biasY);
		viewHolder.setWidgetPosition(virtualVertScroller, splitx+biasX, splity+biasY);
	*/	
		// scroll:
		int vertPos = virtualVertScroller.getScrollPosition();
		int horiPos = virtualHoriScroller.getHorizontalScrollPosition();
		topRightHolder.setWidgetPosition(topRightPart.getHeldPanel(),
				-horiPos, 0);
		botLeftHolder.setWidgetPosition(botLeftPart.getHeldPanel(),
				0, -vertPos);
		botRightHolder.setWidgetPosition(botRightPart.getHeldPanel(),
				-horiPos, -vertPos);
		vertRuler.setViewableTop(vertPos);
		horiRuler.setViewableLeft(horiPos);
		//Rect r = botRightPart.getPosInAppView();
		//viewHolder.setWidgetPosition(botRightHolder, r._left, r._top);
		//botRightHolder.setHeight(Integer.toString(r._height) + "px");
		//botRightHolder.setWidth(Integer.toString(r._width) + "px");
		// set Content Size
		// topLeftPart.getHeldPanel().setHeight()
	}
	private void calcViewableRowCol(Rect pos) {// todo: pos need to be changed when ruler added
		DocModel docModel = AppModel.getInstance().getCurrentDoc();
		int w = pos._width, h = pos._height;
		int fixedX = (Integer)colStarts.get(docModel.getFixedColCount());
		int fixedY = (Integer)rowStarts.get(docModel.getFixedRowCount());
		int mxX = (Integer)colStarts.get(colStarts.size() - 1);
		int mxY = (Integer)rowStarts.get(rowStarts.size() - 1);
		int x = fixedX + virtualHoriScroller.getHorizontalScrollPosition();
		int y = fixedY + virtualVertScroller.getScrollPosition();
		this.viewableColStart = binarySearch(x, colStarts);// the one smaller
		this.viewableColEnd = binarySearch1(Math.min(x+w, mxX), colStarts);
		this.viewableRowStart = binarySearch(y, rowStarts);
		this.viewableRowEnd = binarySearch1(Math.min(y+h, mxY), rowStarts);
		//Window.alert(h+","+viewableRowStart + "," + viewableRowEnd);
		System.out.println(Integer.toString(y+h) +","+Integer.toString(mxY-fixedY));
		// set into gridpart
		topLeftPart.setViewableRowStart(0);
		topLeftPart.setViewableRowEnd(docModel.getFixedRowCount()-1);
		topLeftPart.setViewableColStart(0);
		topLeftPart.setViewableColEnd(docModel.getFixedColCount()-1);
		
		topRightPart.setViewableRowStart(0);
		topRightPart.setViewableRowEnd(docModel.getFixedRowCount()-1);
		topRightPart.setViewableColStart(viewableColStart);
		topRightPart.setViewableColEnd(viewableColEnd);
		
		botLeftPart.setViewableRowStart(viewableRowStart);
		botLeftPart.setViewableRowEnd(viewableRowEnd);
		botLeftPart.setViewableColStart(0);
		botLeftPart.setViewableColEnd(docModel.getFixedColCount()-1);
		
		botRightPart.setViewableRowStart(viewableRowStart);
		botRightPart.setViewableRowEnd(viewableRowEnd);
		botRightPart.setViewableColStart(viewableColStart);
		botRightPart.setViewableColEnd(viewableColEnd);
		
		
		
	}
	int binarySearch(int val, ArrayList vals)
	{
		if (vals.size() == 0)
			return -1;
		int s = 0;
		int e = vals.size()-1;
		int m = (s+e)/2;
		if (val < (Integer)vals.get(s) ||
				val > (Integer)vals.get(e))
			return -1;
		while (true) {
			Iterator itr = vals.listIterator(m);
			if (itr.hasNext() && val > ((Integer)itr.next()) ) {
				//itr.next();
				if (itr.hasNext() && val <= ((Integer)itr.next())){
					return m;
				} else {
					s = m;
				}
			} else {
				e = m;
			}
			//if (s+1 == e)
				//return -1;
			m = (s+e)/2;
		}
	}
	int binarySearch1(int val, ArrayList vals)
	{
		if (vals.size() == 0)
			return -1;
		int s = 0;
		int e = vals.size()-1;
		int m = (s+e)/2;
		if (val < (Integer)vals.get(s) ||
				val > (Integer)vals.get(e))
			return -1;

		while (true) {
			Iterator itr = vals.listIterator(m);
			if (itr.hasNext() && val > ((Integer)itr.next()) ) {
				//itr.next();
				if (itr.hasNext() && val <= ((Integer)itr.next())){
					return m;
				} else {
					s = m;
				}
			} else {
				e = m;
			}
			//if (s+1 == e)
			//	return -1;
			m = (s+e)/2;
		}
	}
	private void setPosition(GridPart gp, Panel holder) {
		Rect r = gp.getPosInAppView();
		viewHolder.setWidgetPosition(holder,
				r._left, r._top);
		holder.setHeight(Integer.toString(r._height) + "px");
		holder.setWidth(Integer.toString(r._width) + "px");
	}
	/**
	 * recalculate the row col starts when the cells' size
	 * information changed.
	 */
	public void reCalculateRowColStarts() {
		DocModel dm = AppModel.getInstance().getCurrentDoc();
		if (dm != null) {
			int r = dm.getRowCount()+1;
			int c = dm.getColCount()+1;
			for (int i = rowStarts.size(); i < r; ++i) {
				rowStarts.add(0);
			}
			for (int i = colStarts.size(); i < c; ++i)
				colStarts.add(0);
			TreeMap tmRow = dm.getRowColMap();
			TreeMap tmCol = dm.getColRowMap();
			int specialCnt = 0;
			int specialSum = 0;
			Iterator itr = tmRow.entrySet().iterator();
			int lastSpecial = 0;
			while (itr.hasNext()) {
				Entry entry = (Entry) itr.next();
				int current = (Integer)entry.getKey();
				TreeMap tm = (TreeMap)entry.getValue();
				if (tm.size() > 0) {
					int height = ((GridCell)tm.get(tm.firstKey())).getHeight();
					specialCnt++;
					specialSum += height;
					// set the row starts
					//if (current < r)
					for (int i = lastSpecial + 1; i < current+1; ++i) {
						int posLast = (Integer)rowStarts.get(i-1);
						rowStarts.set(i, posLast+DocModel.DEFAULT_CELL_HEIGHT);
					}
					rowStarts.set(current+1,
							DocModel.DEFAULT_CELL_HEIGHT * (current+1-specialCnt) + specialSum);
					lastSpecial = current+1;
				}
			}
			for (int i = lastSpecial + 1; i < r; ++i) {
				int posLast = (Integer)rowStarts.get(i-1);
				rowStarts.set(i, posLast+DocModel.DEFAULT_CELL_HEIGHT);
			}
			
			specialCnt = 0;
			specialSum = 0;
			lastSpecial = 0;
			itr = tmCol.entrySet().iterator();
			while (itr.hasNext()) {
				Entry entry = (Entry) itr.next();
				int current = (Integer)entry.getKey();
				TreeMap tm = (TreeMap)entry.getValue();
				if (tm.size() > 0) {
					int width = ((GridCell)tm.get(tm.firstKey())).getWidth();
					specialCnt++;
					specialSum += width;
					// set the row starts
					//if (current < c)
					for (int i = lastSpecial + 1; i < current+1; ++i) {
						int posLast = (Integer)colStarts.get(i-1);
						colStarts.set(i, posLast+DocModel.DEFAULT_CELL_WIDTH);
					}
					colStarts.set(current+1,
							DocModel.DEFAULT_CELL_WIDTH * (current+1-specialCnt) + specialSum);
				}
			}
			for (int i = lastSpecial + 1; i < c; ++i) {
				int posLast = (Integer)colStarts.get(i-1);
				colStarts.set(i, posLast+DocModel.DEFAULT_CELL_WIDTH);
			}
		}
	}
	///////////////////
	// getters and setters
	///////////////
	public ArrayList getColStarts() {
		return colStarts;
	}
	public ArrayList getRowStarts() {
		return rowStarts;
	}
	public AbsolutePanel getViewHolder() {
		return viewHolder;
	}
	public int getViewableColEnd() {
		return viewableColEnd;
	}
	public int getViewableColStart() {
		return viewableColStart;
	}
	public int getViewableRowEnd() {
		return viewableRowEnd;
	}
	public int getViewableRowStart() {
		return viewableRowStart;
	}
	
	public void setColStarts(ArrayList colStarts) {
		this.colStarts = colStarts;
	}
	public void setRowStarts(ArrayList rowStarts) {
		this.rowStarts = rowStarts;
	}
	public void setViewHolder(AbsolutePanel viewHolder) {
		this.viewHolder = viewHolder;
	}
	// or onDrag
	public static native void disableTextSelection(Element element) /*-{ 
    	element.onselectstart = function() {
        	return false; 
    	} 
    	element.onmousedown = function() {
        	return false; 
    	} 
    	
    	//element = null; 
	}-*/;

	public void setViewableColEnd(int viewableColEnd) {
		this.viewableColEnd = viewableColEnd;
	}
	public void setViewableColStart(int viewableColStart) {
		this.viewableColStart = viewableColStart;
	}
	public void setViewableRowEnd(int viewableRowEnd) {
		this.viewableRowEnd = viewableRowEnd;
	}
	public void setViewableRowStart(int viewableRowStart) {
		this.viewableRowStart = viewableRowStart;
	}
	public ScrollPanel getVirtualVertScroller() {
		return virtualVertScroller;
	} 
}
